Skip to content

fix(cargo-wdk): remove network dependency from automatic test signing - #725

Draft
Shravan Vasista (svasista-ms) wants to merge 5 commits into
microsoft:mainfrom
svasista-ms:fix/631-offline-test-signing
Draft

fix(cargo-wdk): remove network dependency from automatic test signing#725
Shravan Vasista (svasista-ms) wants to merge 5 commits into
microsoft:mainfrom
svasista-ms:fix/631-offline-test-signing

Conversation

@svasista-ms

@svasista-ms Shravan Vasista (svasista-ms) commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

signtool sign hard-coded /t http://timestamp.digicert.com, so packaging a driver failed on machines without internet access. Test signatures are trusted only while the signing certificate is valid, but the auto-generated test certificate is valid for a long time, so timestamping them adds little value.

In this PR, the timestamp switch is dropped for test signing and the certificate used in the automated test signing flow is selected by its SHA-1 thumbprint and validity rather than subject name alone. Certificates are now chosen from the WDRTestCertStore listing, reused only when they carry the code-signing EKU and have 90+ days of validity remaining. Otherwise WDRLocalTestCert is created with makecert. A named mutex serializes store access so concurrent builds do not race to create duplicate certificates.

This also removes the early return on an existing WDRLocalTestCert.cer in the target directory, which previously short-circuited the check and masked an expired or deleted store certificate.

Fixes #631

Copilot AI lite review requested due to automatic review settings August 25, 2026 12:18

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates cargo-wdk’s test-signing flow to avoid requiring network access (by removing signtool sign timestamping), and hardens automatic test-certificate selection by choosing a valid code-signing cert from WDRTestCertStore via SHA-1 thumbprint + remaining validity (creating a new cert when needed, and serializing store access with a named mutex).

Changes:

  • Remove /t http://timestamp.digicert.com from test signing and switch signtool to select the cert via /sha1 <thumbprint>.
  • Add store-certificate discovery/selection based on EKU + expiry margin, with parsing of certmgr -v -s output and unit tests for the parser.
  • Update cargo-wdk build tests to match the new cert lookup/export/signing behavior.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
crates/cargo-wdk/src/actions/build/tests.rs Updates mocked command expectations to cover cert lookup sequences and /sha1-based signing.
crates/cargo-wdk/src/actions/build/package_task.rs Implements thumbprint-based certificate selection/export, removes timestamping, and adds certmgr output parsing + tests.
crates/cargo-wdk/src/actions/build/error.rs Adds a dedicated error for “created cert but still couldn’t find a usable one”.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread crates/cargo-wdk/src/actions/build/tests.rs Outdated
Comment thread crates/cargo-wdk/src/actions/build/tests.rs
@codecov-commenter

Codecov Comments Bot (codecov-commenter) commented Aug 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.98678% with 25 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.83%. Comparing base (3655880) to head (a48ac38).

Files with missing lines Patch % Lines
crates/cargo-wdk/src/actions/build/package_task.rs 88.98% 3 Missing and 22 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #725      +/-   ##
==========================================
+ Coverage   82.64%   82.83%   +0.18%     
==========================================
  Files          25       25              
  Lines        6459     6628     +169     
  Branches     6459     6628     +169     
==========================================
+ Hits         5338     5490     +152     
+ Misses        989      988       -1     
- Partials      132      150      +18     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copilot AI review requested due to automatic review settings August 31, 2026 04:37

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

Suppressed comments (2)

Previously missed (1) — in code that hasn't changed since the last review.

crates/cargo-wdk/src/actions/build/package_task.rs:756

  • The doc comment for subject_matches says it matches a subject of exactly CN=<subject>, but the implementation actually compares the RDN value text extracted from the ASCII rendering (e.g., WDRLocalTestCert) and does not include the CN= prefix. Please adjust the comment so it matches what the parser is checking.
    /// Matches a subject of exactly `CN=<subject>`, so a certificate that
    /// merely contains that text in a longer name is not reused.
    fn subject_matches(record: &str, subject: &str) -> bool {
        let Some(subject_section) = section_between(record, "Subject::", "Issuer::") else {
            return false;
        };
        let mut values = subject_section.lines().filter_map(rdn_value);
        values.next() == Some(subject) && values.next().is_none()

crates/cargo-wdk/src/actions/build/tests.rs:2673

  • expect_certmgr_cert_lookup cycles through outputs with % outputs.len() and doesn't constrain the expected call count. That can hide unintended extra certmgr.exe invocations (the mock will keep returning outputs) and make these tests less strict than the other command expectations.
        let mut call_index = 0usize;
        self.mock_run_command
            .expect_run()
            .withf(
                move |command: &str,

Comment thread crates/cargo-wdk/src/actions/build/package_task.rs
Copilot AI review requested due to automatic review settings August 31, 2026 08:48

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

crates/cargo-wdk/src/actions/build/tests.rs:2693

  • expect_certmgr_cert_lookup sets up a single expect_run() without .once()/.times(), but generate_certificate() can call certmgr.exe -v -s WDRTestCertStore twice (before and after creating a cert). With mockall, omitting an explicit call count commonly defaults to a single allowed call, so these tests may fail once the production code performs multiple lookups. Even if the default isn't 1, using % outputs.len() would allow extra unexpected calls to pass. Set an explicit .times(outputs_len) and consume outputs sequentially.
    fn expect_certmgr_cert_lookup(mut self, outputs: Vec<Output>) -> Self {
        assert!(
            !outputs.is_empty(),
            "expect_certmgr_cert_lookup requires at least one output"
        );
        let expected_certmgr_command: &'static str = "certmgr.exe";
        let expected_certmgr_args: Vec<String> = vec![
            "-v".to_string(),
            "-s".to_string(),
            "WDRTestCertStore".to_string(),
        ];
        let mut call_index = 0usize;
        self.mock_run_command
            .expect_run()
            .withf(
                move |command: &str,
                      args: &[&str],
                      _env_vars: &Option<&HashMap<&str, &str>>,
                      _working_dir: &Option<&Path>|
                      -> bool {
                    command == expected_certmgr_command && args == expected_certmgr_args
                },
            )
            .returning(move |_, _, _, _| {
                let index = call_index % outputs.len();
                call_index += 1;
                let output = outputs[index].clone();
                match output.status.code() {
                    Some(0) => Ok(Output {
                        status: ExitStatus::from_raw(0),
                        stdout: output.stdout,
                        stderr: output.stderr,
                    }),
                    _ => Err(CommandError::from_output("certmgr", &[], &output)),
                }
            });

Comment thread crates/cargo-wdk/src/actions/build/package_task.rs
Copilot AI review requested due to automatic review settings September 1, 2026 04:14

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

Suppressed comments (1)

crates/cargo-wdk/src/actions/build/tests.rs:2673

  • expect_certmgr_cert_lookup cycles outputs with % outputs.len(), which can mask regressions where the production code calls certmgr.exe -v -s more times than intended. Returning outputs sequentially and asserting on the call count keeps the test strict and will fail fast if the lookup happens unexpectedly often.
        let mut call_index = 0usize;
        self.mock_run_command
            .expect_run()
            .withf(
                move |command: &str,

Comment on lines +328 to +337
// Default WDR test-cert switches. The signature is deliberately not
// timestamped: the certificate is generated locally for test signing
// only, and requiring a timestamp server would make every build
// depend on network access.
[
"/v",
"/s",
WDR_TEST_CERT_STORE,
"/n",
WDR_LOCAL_TEST_CERT,
"/t",
DEFAULT_TIMESTAMP_URL,
"/sha1",
&thumbprint,
Comment on lines +754 to +756
/// Matches a subject of exactly `CN=<subject>`, so a certificate that
/// merely contains that text in a longer name is not reused.
fn subject_matches(record: &str, subject: &str) -> bool {
Comment on lines +77 to +78
#[error("No usable test certificate was found in the store after creating one")]
NoUsableCertificate,
Copilot AI review requested due to automatic review settings September 1, 2026 04:25

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

crates/cargo-wdk/src/actions/build/tests.rs:2678

  • expect_certmgr_cert_lookup sets up only a single .expect_run() without constraining the call count, but the new certificate flow can call certmgr.exe -v -s WDRTestCertStore multiple times (e.g., before and after creating a cert). The current implementation also cycles outputs with % outputs.len(), which can hide extra unexpected calls and makes the output sequence non-deterministic.

Prefer enforcing the exact number of expected calls and consuming outputs in order (no modulo).

                      args: &[&str],
                      _env_vars: &Option<&HashMap<&str, &str>>,
                      _working_dir: &Option<&Path>|
                      -> bool {
                    command == expected_certmgr_command && args == expected_certmgr_args

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

wdk-build: The signtool-sign build process requires network connection for a little value

3 participants